package main

//Field name     Mandatory?   Allowed values    Allowed special characters
//----------     ----------   --------------    --------------------------
//Seconds        No           0-59              * / , -
//Minutes        Yes          0-59              * / , -
//Hours          Yes          0-23              * / , -
//Day of month   Yes          1-31              * / , - L W
//Month          Yes          1-12 or JAN-DEC   * / , -
//Day of week    Yes          0-6 or SUN-SAT    * / , - L #
//Year           No           1970–2099         * / , -
import (
	"fmt"
	"github.com/gorhill/cronexpr"
	"time"
)

//定义一个定时任务，包括一个解析的cron表达式，包括他的执行时间节点(通过)
type CronJob struct {
	expr         *cronexpr.Expression
	nextExecTime time.Time //实际可通过expr.next(nowTime得到)
}

func main() {
	//首先考虑我们需要一个协程调度，定时检查所有的cron任务（这些cron任务可以放在一个map里，哪个任务到点儿了就执行哪个任务）

	var (
		expr    *cronexpr.Expression
		nowTime time.Time
		cronJob *CronJob
		jobName string
		//定义一个map，存储所有的cronJob
		scheduleTable map[string]*CronJob
	)
	//初始化map需要分配内存，默认size即为0
	scheduleTable = make(map[string]*CronJob)

	//当前时间
	nowTime = time.Now()
	//赋值第一个job1
	expr = cronexpr.MustParse("*/5 * * * * * *")
	cronJob = &CronJob{
		expr: expr,
		//传入当前时间即可得到下次执行时间，算是第一次执行时间的初始化
		nextExecTime: expr.Next(nowTime),
	}
	scheduleTable["job_1_5s"] = cronJob
	//赋值第二个job2
	expr = cronexpr.MustParse("*/6 * * * * * *")
	cronJob = &CronJob{
		expr: expr,
		//传入当前时间即可得到下次执行时间
		nextExecTime: expr.Next(nowTime),
	}
	scheduleTable["job_2_6s"] = cronJob

	//启动一个协程，循环检查任务调度表 scheduleTable
	go func() {
		for {
			nowTime = time.Now()
			for jobName, cronJob = range scheduleTable {
				//如果下次执行时间在当前时间之前（说明已经过期）或者
				if cronJob.nextExecTime.Before(nowTime) || cronJob.nextExecTime.Equal(nowTime) {
					//	启动新的协程，执行具体的任务
					go func(jobName string) {
						fmt.Println("执行", jobName, time.Now())
					}(jobName)
					//	执行完之后，再改写输出计算下一次调度时间
					cronJob.nextExecTime = cronJob.expr.Next(nowTime)
					fmt.Println(jobName, " 下次的执行时间 :", cronJob.nextExecTime)
				}
			}
		}

		//避免for无限循环把cpu打满，这样实现的效果是每过100ms去遍历这个scheduleTable，看是执行还是直接下次执行
		select {
		case <-time.NewTimer(1000 * time.Millisecond).C: //100毫秒后可读，C为chan，通过channel通知到期
		}
		//time.Sleep(100*time.Millisecond)
	}()

	//主协程睡眠
	time.Sleep(20 * time.Second)
}
